package AliDiepPR.ANN;

import AliDiepPR.ANN.Layer;
import AliDiepPR.ANN.Network;
import AliDiepPR.ANN.ActivationFunction.*;

public class BackPropagation {
	IActivationFunction func;

	public IActivationFunction ActivationFunc(IActivationFunction func,
			boolean set) {
		if (set)
			this.func = func;
		return func;
	}

	double rate;

	public double Rate(double val, boolean set) {
		if (set)
			this.rate = val;
		return rate;
	}

	public BackPropagation() {
	}

	public BackPropagation(double alpha, IActivationFunction func) {
		this.func = func;
		this.rate = alpha;
	}

	public void Epoch(Network net, double[][] input, double[][] output) {
		int numLayer = net.numLayer;
		int lay, pre, next;
		double[][][] theta = new double[numLayer - 1][][];

		for (lay = 0; lay < numLayer - 1; lay++) {
			pre = net.Layer(lay).numUnit;
			next = net.layer[lay + 1].numUnit;
			theta[lay] = new double[next][];
			for (int j = 0; j < next; j++)
				theta[lay][j] = new double[pre];
		}

		int exam = input.length;
		for (int ex = 0; ex < exam; ex++) {
			Forward(net, input[ex]);
			BackPropage(net, input[ex], output[ex], theta);
		}

		// =======================================
		//
		// update weight
		//
		// =======================================

		for (lay = 0; lay < numLayer - 1; lay++) {
			pre = net.layer[lay].numUnit;
			next = net.layer[lay + 1].numUnit;

			net.layer[lay].updateWeight(next, theta[lay]);
		}
	}

	public void Forward(Network net, double[] input) {
		double[] wei;
		int numLayer = net.numLayer;
		int lay, pre, next;
		double sum;
		Layer layer, nextLayer;

		// input layer
		lay = 0;
		for (int i = 0; i < net.layer[lay].numUnit; i++)
			net.layer[lay].unit[i].output = input[i];

		// rest layer
		for (lay = 0; lay < numLayer - 1; lay++) {
			layer = net.layer[lay];
			nextLayer = net.layer[lay + 1];
			pre = layer.numUnit;
			next = nextLayer.numUnit;

			for (int j = 0; j < next; j++) {
				wei = layer.getWeightRow(j);
				sum = 0;
				for (int i = 0; i < pre; i++)
					sum += layer.unit[i].output * wei[i];
				sum += nextLayer.unit[j].threshold;

				nextLayer.unit[j].output = func.output(sum);
			}
		}
	}

	public void BackPropage(Network net, double[] input, double[] y,
			double[][][] theta) {
		double[][] delta = new double[net.numLayer][];
		int pre, next, lay, numLayer = net.numLayer;
		Layer layer, nextLayer;

		// =======================================
		//
		// delta
		//
		// =======================================

		// output layer
		lay = numLayer - 1;
		pre = net.layer[lay].numUnit;
		delta[lay] = new double[pre];
		for (int i = 0; i < pre; i++)
			delta[lay][i] = (y[i] - net.layer[lay].unit[i].output)
					* func.output2(net.layer[lay].unit[i].output);

		// rest layer
		double sum = 0;
		for (lay = numLayer - 2; lay > 0; lay--) {
			pre = net.layer[lay].numUnit;
			next = net.layer[lay + 1].numUnit;
			layer = net.layer[lay];
			delta[lay] = new double[pre];

			for (int i = 0; i < pre; i++) {
				sum = 0;
				for (int j = 0; j < next; j++)
					sum += delta[lay + 1][j] * layer.getWeight(j, i);

				delta[lay][i] = sum * func.output2(layer.unit[i].output);
			}
		}

		// =======================================
		//
		// theta_gradient
		//
		// =======================================

		// rest layer
		for (lay = 0; lay <= numLayer - 2; lay++) {
			pre = net.layer[lay].numUnit;
			next = net.layer[lay + 1].numUnit;
			layer = net.layer[lay];
			nextLayer = net.layer[lay + 1];

			for (int j = 0; j < next; j++) {
				for (int i = 0; i < pre; i++)
					theta[lay][j][i] += rate * delta[lay + 1][j]
							* layer.unit[i].output;

				// update threshold
				nextLayer.unit[j].threshold += rate * delta[lay + 1][j];
			}
		}
	}
}
